/**
 * @file nac_cient.c
 * Start NDN NAC on guest computers
 * Chengyu Fan <chengyu@cs.colostate.edu>
 */

#include "common.h"
#include <ares.h>
#include <ctype.h>
#include <arpa/nameser.h>
#include <stddef.h>

#define DHCPACK 5
#define DHCPINFORM 8
#define RR_NAME "_nac-server._udp"
#define BROADCAST "255.255.255.255"
#define MAXBUFLEN 1024
#define SERVERPORT "6867"
#define CLTPORT 6868//currently, use this port for both the local subnet and remote
#define INTERFACENAMELEN 255

#define DHO_NDN_NAC 246
#define DHO_NDN_GATEWAY 247
#define DHO_NDN_NAMESPACE 248

#define NACSERVER 0
#define NDNPARA 1

#define TIMEOUT 5
#define MAXRETRYTIME 3

#define NDN_NAMESPACE_FILE "/etc/ndn_namespace.conf"
#define NAC_AGENT "_nac-agent._udp"

int verbose = 0;
u_int32_t my_xid; //transaction id
FILE *LOG;

struct agent_msg_header{
    uint16_t version;
    uint16_t length;
};

struct prefixes{
    char content[255];
    struct prefixes *next;
};

struct nac{
    unsigned afi;
    char ip[INET6_ADDRSTRLEN];
    struct nac *next;
};

struct ndngw{
    char addr[INET6_ADDRSTRLEN];
    char port[10];
    unsigned protocol;
    struct prefixes *nameprefix;
    struct ndngw *next;
};

struct agent{
    char host[255];
    unsigned short weight;
    unsigned short priority;
    unsigned short port;
    struct agent *next;
};

struct ccndc_data
{
    struct ccn          *ccn_handle;
    char                ccnd_id[32];       //id of local ccnd
    size_t              ccnd_id_size;
    int                 lifetime;
    struct ccn_charbuf  *local_scope_template; // scope 1 template
    struct ccn_charbuf  *no_name;   // an empty name
};

struct mydata{
    struct ndngw *gws;
    struct prefixes *namelist;
    struct nac *naclist;
    struct agent *agents;
    int state;
    int remote;
    char *myip;
    char *id;
    char *dst;
    char *dst_port;
    char *myport;
    char *domain;
    char *fallback_domain;
    int timeout;
    int retry_times;
    struct ccn *h;
};

int send_inform(struct mydata *md);

void
usage(char *program){
    printf("usage:%s <-a ccnd_ip> <-p ccnd_port> [-s nacs_string] [-g ndn_gateway_string] [-n namespace_string] [-d domain] [-l log_file] [-f file] [-v] [-h]\n"
         "\t-a the IP address on which the ccnd is running\n"
         "\t-p the port number on which the ccnd is running\n"
         "\t-s the DHCP OPTION string that carries the local NAC servers' information\n"
         "\t-g the DHCP OPTION string that carries the local NDN gateways' information\n"
         "\t-n the DHCP OPTION string that carries the user's name space\n"
         "\t-d the DHCP OPTION string that indicates current domain name\n"
         "\t-l specify the log file, default is standard output\n"
         "\t-v verbose mode - enable debug messages\n"
         "\t-f specify the configuration file that configures fallback domain\n"
         , program);
    exit(1);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void
log_info(FILE *dst, const char *fmt, ...){
    if (dst == NULL)
        return;

    char log[1024];
    va_list ap;
    struct timeval t;
    gettimeofday(&t, NULL);

    va_start(ap, fmt);
    vsprintf(log, fmt, ap);
    va_end(ap);

    fprintf(dst, "%d.%06d, join_ndn[%d]: %s\n", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), log);    
}

void
free_nacs(struct mydata *md){
    struct nac *cur = md->naclist, *tmp;
    if(cur == NULL)
        return;
    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;
        free(cur);
        cur = tmp;
    }
    md->naclist = NULL;
}

void
free_namespaces(struct mydata *md){
    struct prefixes *cur = md->namelist, *tmp;
    if(cur == NULL)
        return;

    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;
        free(cur);
        cur = tmp;
    }

    md->namelist = NULL;
}

void
free_gws(struct mydata *md){
    struct ndngw *cur = md->gws, *tmp;
    if(cur == NULL)
        return;

    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;

        /*free all the prefix belongs to this gateway*/
        struct prefixes *pp = cur->nameprefix, *temp;
        if (pp == NULL)
            continue;
        while (pp != NULL){
            temp = pp->next;
            pp->next = NULL;

            free(pp);
            pp = temp;
        }
      
        free(cur);
        cur = tmp;
    }

    md->gws = NULL;
}

void
free_agents(struct mydata *md){
    struct agent *cur = md->agents, *tmp;
    if(cur == NULL)
        return;
    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;
        free(cur);
        cur = tmp;
    }
    md->agents = NULL;
}

void
print_nacs(struct mydata *md){
    struct nac *l = md->naclist;
    printf("\n\n*******<NAC servers>*******\n");
    while(l != NULL){
        printf("[NAC addr] %s\n", l->ip);
        l = l->next;
    }
    printf("\n");
}

void
print_gws(struct mydata *md){
    struct ndngw *cur = md->gws;
    printf("\n\n*******<NDN Gateways>*******\n");

    while(cur != NULL){
        printf("(Gateway) %s:%s\n", cur->addr, cur->port);

        /*print all the prefix belongs to this gateway*/
        struct prefixes *pp = cur->nameprefix;
        if (pp == NULL)
            continue;

        while (pp != NULL){
            printf("[prefix] %s\n",pp->content);
            pp = pp->next;
        }
        /*prefix free ends*/
      
        cur = cur->next;
        printf("\n");
    }
    printf("\n");
}

void
print_namespace(struct mydata *md){
    printf("================================\n");
    printf("Your namespace is listed below:\n");
    printf("================================\n");
    struct prefixes *namespace = md->namelist;
    while(namespace != NULL){
        printf("%s/%s ", namespace->content, md->id);
        namespace = namespace->next;
    }
    printf("\n");
}

int
try_nacs(struct mydata *md){
    if(md == NULL || md->myip == NULL)
        return 1;
    int res = 1;
    md->state = NDNPARA;//unicast
    md->timeout = TIMEOUT;
    md->retry_times = MAXRETRYTIME;
    struct nac *cur = md->naclist;
    while(cur != NULL){
        md->dst = cur->ip;
        md->dst_port = SERVERPORT;
        
        if(verbose != 0)
            log_info(LOG, "Try to get NDN paras from NAC server %s", md->dst);
        res = send_inform(md);
        if (res == 0)
            break;

        cur = cur->next;
    }

    return res;
}

static void
state_cb(void *data, int s, int read, int write){
    //printf("Change state fd %d read:%d write:%d\n", s, read, write);
}

static void
srv_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen){
    struct ares_srv_reply *srv_out;
    struct ares_srv_reply **rr = (struct ares_srv_reply **)arg;

    if(abuf == NULL || status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "SRV - Failed to lookup (%s)", ares_strerror(status));
        *rr = NULL;
        return;
    }

    status = ares_parse_srv_reply(abuf, alen, &srv_out);
    if(status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG,"SRV - Failed to parse (%s)\n", ares_strerror(status));
        return;
    }

    *rr = srv_out;
}

static void
name_callback(void *arg, int status, int timeouts, struct hostent *host){
    struct nac *server = (struct nac *)arg;
   
    if(!host || status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "NAME - Failed to lookup %s\n", ares_strerror(status));
        server = NULL;
        return;
    }
    char ip[INET6_ADDRSTRLEN];
    int i;

    for (i = 0; host->h_addr_list[i]; i++) {
        inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
        server->afi = host->h_addrtype;
        strcpy(server -> ip, ip);
    }
}

static void
wait_ares(ares_channel channel){
    for(;;){
        struct timeval *tvp, tv;
        tv.tv_sec = 3;
        tv.tv_usec = 0;
        fd_set read_fds, write_fds;
        int nfds, retval;

        FD_ZERO(&read_fds);
        FD_ZERO(&write_fds);
        nfds = ares_fds(channel, &read_fds, &write_fds);
        if(nfds == 0){
            break;
        }
        tvp = ares_timeout(channel, NULL, &tv);
        retval = select(nfds, &read_fds, &write_fds, NULL, tvp);
        if (retval == -1 && verbose == 1)
            perror("select()");
        else if (retval == 0){
            if (verbose == 1)
                log_info(LOG, "Did not recv DNS data in 3 seconds");
            break;
        }
            
        ares_process(channel, &read_fds, &write_fds);
    }
}

int
get_dns_nacs(const char *domain, struct mydata *md){
    char service[255];
    int status;
    ares_channel channel;
    struct ares_options options;
    int optmask = 0;
    struct ares_srv_reply *rr = NULL;

    status = ares_library_init(ARES_LIB_INIT_ALL);
    if(status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "DNS - ares_library_init: %s", ares_strerror(status));
        return 1;
    }

    options.sock_state_cb = state_cb;
    optmask |= ARES_OPT_SOCK_STATE_CB;

    status = ares_init_options(&channel, &options, optmask);
    if(status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "DNS - ares_init_options: %s", ares_strerror(status));
        return 1;
    }

    status = sprintf(service, "%s.%s", RR_NAME, domain);
    if(status < 0){
        if (verbose != 0)
            log_info(LOG, "DNS - cannot construct SRV_Name");
        return 1;
    }

    if (verbose != 0)
        log_info(LOG, "service querying: %s", service);
    /*get the nac server list*/
    ares_query(channel, service, ns_c_in, ns_t_srv, srv_callback, &rr);
    wait_ares(channel);

    if(rr != NULL){//reorder the nacs according to the priority
        //choose one server: now we have the server list, choose the lower priority value
        struct ares_srv_reply *tmp, *head, *tail;
        head = rr;
    
        while(head != NULL){
            tail = head->next;
            while(tail != NULL){    
                if(head->priority > tail->priority){
                    tmp = tail->next;
                    tail->next = head;
                    head->next = tmp;
                }
                tail = tail -> next;   
            }
            head = head -> next;
        }
    
        //copy this list to nac_list
        head = rr;

        struct nac *p = md->naclist;
        if(p == NULL){//malloc space for nac
            p = (struct nac*)malloc(sizeof (struct nac));
            p->next = NULL;
            md->naclist = p;
        }

        while(head != NULL && p != NULL){
            //parse the servers' hostname into ip address
            ares_gethostbyname(channel, head -> host, AF_INET, name_callback, p);
            wait_ares(channel);
 
            if(p->next == NULL && head->next != NULL){
                p->next = (struct nac*)malloc(sizeof (struct nac));        
                p->next->next = NULL;
            }

            head = head -> next;
            p = p->next;
        }

        ares_free_data(rr);
        ares_destroy(channel);
        ares_library_cleanup();

        return 0;
    }
    else{
        ares_destroy(channel);
        ares_library_cleanup();
    
        return 1;
    }
}

//this func is only for packet not more than MAX
int is_valid_inform_reply(struct dhcp_packet *packet, int len){
    //if the transaction ID is wrong, or it is not the reply, it is invalid
    if(packet->xid != my_xid || packet->op != BOOTREPLY)
        return -1;

    //if the msgType is not ACK, it is invalid
    unsigned char options[DHCP_MAX_OPTION_LEN];
    memcpy(options, packet->options, DHCP_MAX_OPTION_LEN);

    //parse options here
    int opt_len = len - DHCP_FIXED_NON_UDP;
    //the first 4 is the magic cookie
    int index = 4;
    u_int8_t code, l;
    while(index < opt_len - 1){//the last one is the END(255)
        code = options[index];
        if(code == DHO_END)//this is the end
            return -1;

        index ++;
        l = options[index];
        index ++;

        if(code == DHO_DHCP_MESSAGE_TYPE && options[index] == DHCPACK)
            return 0;//this is the valid msg

        index = index + l;//after the last option
    }
    return -1;
}

//convert a struct sockaddr addr to a string, IPv4 and IPv6
char *
get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen){
    switch(sa->sa_family){
        case AF_INET:
            inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                    s, maxlen);
            break;

        case AF_INET6:
            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                    s, maxlen);
            break;

        default:
            strncpy(s, "unknown AF", maxlen);
            return NULL;
    }

    return s;
}

int
parse_nacs(struct mydata *md, unsigned char *options, 
        int index, int l){
    int offset = 0;
    unsigned char *nac_buf = (unsigned char*)malloc(l);
    memcpy(nac_buf, options + index, l);

    unsigned count = nac_buf[offset];
    offset ++;
    unsigned i = 0;

    while (i < count){
        if (offset >= l)
            break;
        struct nac *nacserver = md->naclist;
        if(nacserver == NULL){//make sure that the 1st one has space
            //malloc a new space for new nac
            nacserver = (struct nac*)malloc(sizeof (struct nac));
            nacserver->next = NULL;
            md->naclist = nacserver;
        }else{                 
            struct nac *prev = NULL;
            //find the right position to add a new nac
            while(nacserver != NULL){
                prev = nacserver;
                nacserver = nacserver->next;
            }
            //malloc a new space for new nac
            nacserver = (struct nac*)malloc(sizeof (struct nac));
            nacserver->next = NULL;
            prev->next = nacserver;
        }

        unsigned afi = nac_buf[offset];
        offset ++;

        struct sockaddr *sa = NULL;
        if(afi == 0){//this is ip v4
            struct sockaddr_in nac_addr;
            nac_addr.sin_family = AF_INET;
        
            memcpy(&(nac_addr.sin_addr), nac_buf + offset, sizeof(struct in_addr));
            sa = (struct sockaddr*)&nac_addr;
            get_ip_str(sa, nacserver->ip, INET_ADDRSTRLEN);
            nacserver->afi = afi;

            offset += sizeof (struct in_addr);
        }

        if(afi == 1){//ipv6
            struct sockaddr_in6 nac_addr;
            nac_addr.sin6_family = AF_INET6;
        
            memcpy(&(nac_addr.sin6_addr), nac_buf + offset, sizeof(struct in6_addr));
            sa = (struct sockaddr*)&nac_addr;
            get_ip_str(sa, nacserver->ip, INET6_ADDRSTRLEN);
            nacserver->afi = afi;

            offset += sizeof (struct in6_addr);
        }

        i ++;
    }

    free(nac_buf);

    if(offset != l){//only when offset = l, the option is valid
        free_nacs(md);
        return 1;
    }
    return 0;
}

int
parse_gws(struct mydata *md, unsigned char *options, 
        int index, int l){
    int offset = 0;
    unsigned char *ndn_buf = (unsigned char*)malloc(l);
    memcpy(ndn_buf, options + index, l);
    unsigned gw_count = ndn_buf[offset];
    offset ++;

    unsigned i = 0;
    while(i < gw_count){
        struct ndngw *gws = md->gws, *prev;
        unsigned protocol = ndn_buf[offset];
        offset ++;
        unsigned afi = ndn_buf[offset];
        offset ++;

        if(gws == NULL){//make sure that the 1st one has space
            //malloc a new space for new gw
            gws = (struct ndngw*)malloc(sizeof (struct ndngw));
            gws->next = NULL;
            gws->nameprefix = NULL;
            md->gws = gws;
        }else{                 
            prev = NULL;
            //find the right position to add a new gw
            while(gws != NULL){
                prev = gws;
                gws = gws->next;
            }
            //malloc a new space for new gw
            gws = (struct ndngw*)malloc(sizeof (struct ndngw));
            gws->next = NULL;
            gws->nameprefix = NULL;
            prev->next = gws;
        }

        gws->protocol = protocol;
        struct sockaddr *sa;
        if(afi == 0){
            struct sockaddr_in gw_addr;
            gw_addr.sin_family = AF_INET;
        
            memcpy(&(gw_addr.sin_addr), ndn_buf + offset, sizeof(struct in_addr));
            sa = (struct sockaddr*)&gw_addr;
            get_ip_str(sa, gws->addr, INET_ADDRSTRLEN);

            offset += sizeof (struct in_addr);
        }

        if(afi == 1){
            struct sockaddr_in6 gw_addr;
            gw_addr.sin6_family = AF_INET6;
        
            memcpy(&(gw_addr.sin6_addr), ndn_buf + offset, sizeof(struct in6_addr));
            sa = (struct sockaddr*)&gw_addr;
            get_ip_str(sa, gws->addr, INET6_ADDRSTRLEN);
            offset += sizeof (struct in6_addr);
        }

        /*handle port number*/
        uint16_t gw_port;
        memcpy(&gw_port, ndn_buf + offset, 2);//port
        offset += 2;
        sprintf(gws->port, "%d", ntohs(gw_port));

        /*namespaces*/
        uint16_t name_num;
        memcpy(&name_num, ndn_buf + offset, 2);//name_num
        offset += 2;

        int j = 0;
        while(j < ntohs(name_num)){
            /*always make sure there is a space to store the name*/
            struct prefixes *pos = gws->nameprefix;
            if(pos == NULL){
                pos = (struct prefixes*)malloc(sizeof (struct prefixes));
                pos->next = NULL;
                gws->nameprefix = pos;
            }else{
                struct prefixes *last;
                while(pos != NULL){
                    last = pos;
                    pos = pos->next;
                }
                pos = (struct prefixes*)malloc(sizeof (struct prefixes));
                pos->next = NULL;
                last->next = pos;
            }

            uint16_t name_len;// = ndn_buf[offset];
            memcpy(&name_len, ndn_buf + offset, 2);//name_len
            offset += 2;

            if ((offset + ntohs(name_len)) <= l)
                memcpy(pos->content, ndn_buf + offset, ntohs(name_len));
            else
                goto out;

            pos->content[ntohs(name_len)] = '\0';
            offset += ntohs(name_len);

            j ++;
        }
        i ++;
    }

out:
    free(ndn_buf);
    if(offset != l){//only when offset = l, the option is valid
        free_gws(md);
        return 1;
    }

    return 0;
}

int
parse_namespaces(struct mydata *md, unsigned char *options, 
        int index, int l){
    struct prefixes *name = md->namelist;
    if (name == NULL){
        name = (struct prefixes *)malloc(sizeof (struct prefixes));
        name->next = NULL;
        md->namelist = name;
    }
    memcpy(name->content, options + index, l);
    name->content[l] = '\0';
    //printf("namespace: %s %p %p\n", name->content, name, md->namelist);
    return 0;
}

//target indicating the parsed option, 0=nacserver, 1=ndngw
int
parse_options(struct dhcp_packet *packet, struct mydata *md){
    int target = md->state, res = 1; 
    int flag = -1;//successful?
    int flag_gateway = 0;//indicating whether we get the NDN option
    int flag_name = 0;//indicating whether we get the domain option
    int flag_nacserver = 0;
    unsigned char options[DHCP_MAX_OPTION_LEN];
    //struct ndn_data inform_reply;
    memcpy(options, packet->options, DHCP_MAX_OPTION_LEN);

    //I only care 246 and Domain name
    //the first 4 is the magic cookie
    int index = 4;
    unsigned code, l;//l is the length of the value
    while(index < DHCP_MAX_OPTION_LEN - 1){//the last one is the END(255)
        code = options[index];
        if(code == DHO_END)//this is the end
            break;

        index ++;
        l = options[index];
        index ++;

        if(code == DHO_NDN_NAC){
            res = parse_nacs(md, options, index, l);
            if (res == 1)
                return res;
            flag_nacserver = 1;
        }

        if(code == DHO_NDN_NAMESPACE){//namespace
            parse_namespaces(md, options, index, l);
            flag_name = 1;
        }

        if(code == DHO_NDN_GATEWAY){//ndn, now the format must be
            //code Len(1), count (1), AFI(1), IP(4 or 6), Port(2), NameNo(2)
            //Name Len(2), Nameprefix
            res = parse_gws(md, options, index, l);
            if (res == 1)
                return res;
            flag_gateway = 1;
        }

        index = index + l;//after the last option
    }

    if(flag_gateway == 1 && flag_name == 1 && target == NDNPARA){
        //save the parameters in a file
        //save_para(&inform_reply);
        flag = 0;
    }
    if(flag_nacserver == 1 && target == NACSERVER){
        flag = 0;
    }
    return flag;
}

int
make_options(struct dhcp_packet *packet, int flag){
    int offset = 0;
    //construct options here
    unsigned char options[DHCP_MAX_OPTION_LEN];
    memset(options, 0, DHCP_MAX_OPTION_LEN);

    //cpy the magic cookie(start of options)
    memcpy(options, DHCP_OPTIONS_COOKIE, 4);
    offset += 4;

    //option 53, msg Type, we must contain this one
    struct option_header oh;
    oh.code = DHO_DHCP_MESSAGE_TYPE;
    oh.len = 1;
    memcpy(options + offset, &oh, sizeof (struct option_header));
    offset += sizeof (struct option_header);
    options[offset] = DHCPINFORM; 
    offset ++;

    //option 55, request list
    memset(&oh, 0, sizeof (struct option_header));
    oh.code = DHO_DHCP_PARAMETER_REQUEST_LIST;
    if (flag == NACSERVER){
        oh.len = 1;//only need 246(NAC server)
        memcpy(options + offset, &oh, sizeof (struct option_header));
        offset += sizeof (struct option_header);
        options[offset] = DHO_NDN_NAC;//this one should be the NDN option
        offset ++;
    }else if (flag == NDNPARA){
        oh.len = 2;//right now, we need name and gw
        memcpy(options + offset, &oh, sizeof (struct option_header));
        offset += sizeof (struct option_header);
        options[offset] = DHO_NDN_NAMESPACE;//this one should be the NDN option
        offset ++;
        options[offset] = DHO_NDN_GATEWAY;
        offset ++;
    }
    //the end of options
    options[offset] = DHO_END;
    offset ++;

    //make the packet as word boundary
    if(offset % 4 != 0){
        offset = offset + 4 - (offset % 4);
    }
    memcpy(packet->options, options, offset);

    return offset;
}


void 
make_common_parts(struct dhcp_packet *packet, char *myip){
    int rv;
    //get the hardware address by ip
    char face_name[INTERFACENAMELEN];
    get_face_name(face_name, myip);
    struct hardware myhw;
    get_hw_addr(face_name, &myhw);

    //assign value
    packet->op = BOOTREQUEST;
    packet->htype = myhw.hbuf[0]; //assuming it is a ethernet card
    packet->hlen = myhw.hlen - 1;//assuming it is ethernet card
    packet->hops = 0;
    packet->xid = my_xid;
    packet->secs = 0;
    packet->flags = 0;//assuming this interface can use unicast way, but we need another mechanism to test, and then set it
    memset(&(packet->ciaddr), 0, sizeof (packet->ciaddr));
    
    //assign my current ip
    struct in_addr ciaddr;
    if((rv = inet_aton(myip, &ciaddr)) == 0){
        if (verbose != 0)
            log_info(LOG, "Cannot convert IP");
        exit(1);
    }
    packet->ciaddr = ciaddr;

    //to us, those parts are 0
    memset(&(packet->yiaddr), 0, sizeof (packet->yiaddr));
    memset(&(packet->siaddr), 0, sizeof (packet->siaddr));
    memset(&(packet->giaddr), 0, sizeof (packet->giaddr));

    //cpy the hardware address
    memset(&(packet->chaddr), 0, sizeof (packet->chaddr));
    if(myhw.hlen > 0)
        memcpy(&(packet->chaddr), &(myhw.hbuf[1]), (unsigned)(myhw.hlen - 1));
}


void
init_packet(struct dhcp_packet *packet){
    memset(packet, 0, sizeof (struct dhcp_packet));
}


void
set_sock_opt(int sockfd, struct sockaddr *sa){
    int broadcast = 1, reuse = 1;
    // this call is what allows broadcast packets to be sent:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
        sizeof broadcast) == -1) 
    {
        if (verbose != 0)
            log_info(LOG, "setsockopt (SO_BROADCAST)");
        exit(1);
    }

    // this call is what allows reuse port:
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse,
        sizeof reuse) == -1) 
    {
        if (verbose != 0)
            log_info(LOG, "setsockopt (SO_REUSEADDR)");
        exit(1);
    }

#if defined(__APPLE__) && defined(__MACH__)
    int port = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &port,
        sizeof port) == -1) 
    {
        if (verbose != 0)
            log_info(LOG, "setsockopt (SO_REUSEPORT)");
        exit(1);
    }
#endif
    
    if( -1 == bind(sockfd, (struct sockaddr *)sa, sizeof(struct sockaddr)) ){
        if (verbose != 0)
            log_info(LOG, "Cannot bind socket: %s", strerror(errno));
        exit(1);
    }
}

//Convert a string to a struct sockaddr address, IPv4 and IPv6:
void get_sockaddr(const struct sockaddr *sa, char *ip)
{
    switch(sa->sa_family) {
        case AF_INET:
            inet_pton(AF_INET, ip, &(((struct sockaddr_in *)sa)->sin_addr));
            //(((struct sockaddr_in *)sa)->sin_addr).s_addr = INADDR_ANY;
            ((struct sockaddr_in *)sa)->sin_port = htons(CLTPORT);
            memset(((struct sockaddr_in *)sa)->sin_zero, '\0', sizeof (((struct sockaddr_in *)sa)->sin_zero));
            break;

        case AF_INET6:
            inet_pton(AF_INET6, ip, &(((struct sockaddr_in6 *)sa)->sin6_addr));
            ((struct sockaddr_in6 *)sa)->sin6_port = htons(CLTPORT);
            break;

        default:
            if (verbose != 0)
                log_info(LOG, "Unknown AF");
            exit(1);
    }
}

int get_ip_type(char *ip){
    if (ip == NULL)
        return -1;//empty

    int len = strlen(ip);
    int i = 0;
    while (i < len){
        if (ip[i] == ':')
            return 1;//v6
        i ++;
    }

    return 0;//v4
}

//need local ip and structure to store info
int 
send_inform(struct mydata *md){
    char *myip = md->myip;
    char *srv = md->dst;
    if(myip == NULL || srv == NULL)
        return 1;
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv, retry = 0;
    int numbytes;
    char buf[MAXBUFLEN];

    //generate the transaction no.
    my_xid = rand();
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(srv, md->dst_port, &hints, &servinfo)) != 0) {
        if (verbose != 0)
            log_info(LOG, "getaddrinfo: %s", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            continue;
        }
        break;
    }

    if (p == NULL) {
        if (verbose != 0)
            log_info(LOG, "failed to bind socket");
        return 1;
    }

    //convert the src IP to a structure
    int ip_type = get_ip_type(myip);
    if (ip_type == -1){
        if (verbose != 0)
            log_info(LOG, "You input a wrong IP address");
        exit(1);
    }

    struct sockaddr sa;
    if (ip_type == 0){//v4
        sa.sa_family = AF_INET;
    }else if (ip_type == 1){
        sa.sa_family = AF_INET6;
    }
    get_sockaddr(&sa, myip);

    //let the socket can listening on 68, and broadcast
    set_sock_opt(sockfd, &sa);

    //create the dhcp packet structure
    struct dhcp_packet out_packet;
    init_packet(&out_packet);

    //make the common parts, such as xid, ciadr et al.
    make_common_parts(&out_packet, myip);
    
    //make the options part, but right now only support domain name and private 246
    int offset;
    if(md->state == 0)
        offset = make_options(&out_packet, NACSERVER);
    else
        offset = make_options(&out_packet, NDNPARA);

    //DHCP_FIXED_NON_UDP includes all the field from op to the end of file, offset is the length of options
    int msg_len = DHCP_FIXED_NON_UDP + offset;
    printf("[join_ndn]: Please wait ...\n");
redo:
    memset(buf, 0, MAXBUFLEN);
    memcpy(buf, &out_packet, msg_len);

    if (verbose != 0)
        log_info(LOG, "Send DHCPINFORM msg from %s to %s", myip, srv);

    //send packet
    if ((numbytes = sendto(sockfd, buf, msg_len, 0, p->ai_addr, p->ai_addrlen)) == -1) {
        if (verbose != 0)
            log_info(LOG, "failed to send data");
        close(sockfd);
        return 1;
    }

    //read from remote server, maybe we need to get its addr for the latter use
    struct sockaddr_storage their_addr;
    socklen_t addr_len = sizeof their_addr;
wait:
    if(readable_time(sockfd, md->timeout) == 0){//need to add retry
        if (verbose != 0)
            log_info(LOG, "timeout ...");
        if(retry < md->retry_times){
            retry ++;
            goto redo;
        }
    }else{
           memset(buf, 0, MAXBUFLEN);
        if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN, 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            if (verbose != 0)
                log_info(LOG, "receive failed");
            close(sockfd);
            return 1;
        }

        //get src addr
        char src_addr[INET6_ADDRSTRLEN];

        if (their_addr.ss_family == AF_INET){
            struct sockaddr_in *sock = (struct sockaddr_in *)&their_addr;
            inet_ntop(AF_INET, &(sock->sin_addr), src_addr, INET_ADDRSTRLEN);
        }

        if (their_addr.ss_family == AF_INET6){
            struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&their_addr;
            inet_ntop(AF_INET6, &(sock->sin6_addr), src_addr, INET6_ADDRSTRLEN);
        }

        struct dhcp_packet in_packet;
        init_packet(&in_packet);
        memcpy(&in_packet, buf, numbytes);

        //check if this message is the right ACK, on the same port, dhclient may send out request and get ack or offer
        if((rv = is_valid_inform_reply(&in_packet, numbytes)) == 0){
            if (verbose != 0)
                log_info(LOG, "valid ACK message from %s", src_addr);
            rv = parse_options(&in_packet, md);
            if(rv != 0)//when md->state is 0, we need a timer to say if it is time out
                goto wait;
        }
        else{
            if (verbose != 0)
                log_info(LOG, "invalid ACK message from %s", src_addr);
            goto wait;
        }
    }
    
//out:
    freeaddrinfo(servinfo);
    close(sockfd);

    if(retry >= md->retry_times)
        return 1;

    return 0;
}

int
get_inform_nacs(struct mydata *md){
    int res;
    md->dst = BROADCAST;
    md->dst_port = SERVERPORT;
    md->state = NACSERVER;//broadcast
    md->timeout = TIMEOUT;
    md->retry_times = MAXRETRYTIME;
    res = send_inform(md);
    return res;
}

void
init_list(struct mydata *md){
    free_nacs(md);
    free_gws(md);
    free_namespaces(md);
    free_agents(md);
    if (md->id != NULL){
        free(md->id);
    }
}

unsigned char *
str2hex(char * str, int *length){
    if (strlen(str) == 0 || str == NULL){
        *length = 0;
        return NULL;
    }

    const char delimiters[] = " .,;:!-";
    char *token;//, *running;
    int len = strlen(str), i = 0, count = 0;
    while(i < len) {
        if(str[i] == ':')
            count ++;
        i ++;
    }    

    unsigned char *buf = (unsigned char *)malloc(count + 1);
    //printf("i = %d, count = %d, len = %d, %p\n", i, count, len, buf);
    *length = count + 1;
    i = 0;
    //running = strdupa (str);                /* Make writable copy.  */
    while((token = strsep(&str, delimiters)) != NULL){
        unsigned tmp;
        sscanf(token, "%x", &tmp);
        buf[i] = tmp;
        i ++;
    }
    return buf;
}

/*---------------------connection start-----------------------*/
void setCltAddrinfo(struct addrinfo *hints){
    memset(hints, 0, sizeof (struct addrinfo));
    hints->ai_family = AF_UNSPEC;
    hints->ai_socktype = SOCK_DGRAM;
}

struct pheader *get_header(uint8_t type){
    //construct the packet header
    struct pheader *header = (struct pheader *)malloc(sizeof (struct pheader));
    header->ver = 64;//01000000, the first 2 bits are version, the 3rd one indicates v4 or v6
    header->msgType = type;
    header->length = 0;
    return header;
}

struct packet_act *get_packet_act(struct mydata *md, int protocol){
    char *myip = md->myip;
    uint16_t port = atoi(md->myport) & 0xffff;
    uint8_t nameLen = strlen(md->namelist->content) & 0xff;
    uint8_t prot = protocol & 0xff;

    //construct the packet content
    struct packet_act *act = (struct packet_act *)malloc(sizeof (struct packet_act));
    struct in_addr node_addr;
    int res = inet_aton(myip, &node_addr);
    if (res == 0){
        printf("inet_aton error\n");
        exit(1);
    }
    memcpy(act, &node_addr, sizeof (struct in_addr));
    act->port = htons(port);
    act->nameLen = nameLen;
    act->prot = prot;
    return act;
}

int is_valid_act_response(struct pheader *header){
    if((header->ver & 0x40) == 64 && (header->msgType == ACTACK || header->msgType == ACTNACK))
        return 0;
    return -1;
}

struct ndngw *get_gw_by_addr(char *addr, struct mydata *md){
    //find the place that can add gw
    struct ndngw *pos = md->gws;

    while(pos != NULL){
       if(strcmp(pos -> addr, addr) == 0)
           return pos;
       pos = pos -> next;
    }
    return NULL;
}

void
construct_fib2gw(char *gw, char *ccnd_addr, unsigned port, struct mydata *md){
    struct ndngw *pos = md->gws;
    
    while(pos != NULL){
        if(strcmp(pos->addr, gw) == 0){
            struct prefixes *list = pos->nameprefix;
            while(list != NULL){
                char command[255];
                sprintf(command, "ccndc add %s udp %s %u", list->content, ccnd_addr, port);
                if (verbose != 0)
                    log_info(LOG, "Execute command [%s]", command);
                int res = system(command);
                list = list->next;
            }
        }
        pos = pos->next;
    }
/*
    FILE *fp = fopen(NDN_NAMESPACE_FILE, "w");
    if(fp == NULL){
        if (verbose != 0)
            log_info(LOG, "cannot open file %s", NDN_NAMESPACE_FILE);
        exit(1);
    }

    fprintf(fp, "NDN_NAMESPACE %s\n", md->namelist->content);
    fclose(fp);
*/
}

/*
int
connect_ndn(struct mydata *md, char *gwaddr, char *gwport){
    if(md == NULL || gwaddr == NULL || gwport == NULL)
        return 1;
    int sockfd, res;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct pheader *header = NULL;

    //setup connection to the ccndaemon
    md->h = ccn_create();
    struct ccn *h = md->h;
    res = ccn_connect(h, NULL);
    if (res < 0) {
        if (verbose != 0)
            log_info(LOG, "Cannot connect to ccnd.");
        return 1;
    }

    //set the addrinfo struct
    p = &hints;
    setCltAddrinfo(p);

    if ((rv = getaddrinfo(gwaddr, gwport, &hints, &servinfo)) != 0) {
        if (verbose != 0)
            log_info(LOG, "getaddrinfo: %s", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            if (verbose != 0)
                log_info(LOG, "socket create fails");
            continue;
        }

        break;
    }

    if (p == NULL) {
        if (verbose != 0)
            log_info(LOG, "failed to bind socket");
        return 1;
    }

    //header 0
    header = get_header(MSGACT);

    //content
    struct packet_act *act = NULL;
    act = get_packet_act(md, UDP);

    //create buffer and save info here
    char *buf = (char *)malloc(MAXBUFLEN);
    int offset = 0;
    offset += sizeof (struct pheader);
    memcpy(buf+offset, act, sizeof (struct packet_act));
    offset += sizeof (struct packet_act);
    memcpy(buf+offset, md->namelist->content, strlen(md->namelist->content));
    offset += strlen(md->namelist->content);

    //cpy the header
    header->length = htons(offset);
    memcpy(buf, header, sizeof (struct pheader));

    if (verbose != 0)
        log_info(LOG, "Send activate msg to %s: %s", gwaddr, gwport);
    if ((numbytes = sendto(sockfd, buf, offset, 0,
             p->ai_addr, p->ai_addrlen)) == -1) {
        if (verbose != 0)
            log_info(LOG, "sendto fails");
        return 1;
    }

    //receive gw's response
    struct sockaddr_storage their_addr;
    socklen_t addr_len = sizeof their_addr;
    memset(buf, 0, MAXBUFLEN);

    if(readable_time(sockfd, TIMEOUT) == 0){
        if (verbose != 0)
            log_info(LOG, "Activate timeouts");
    }else{
        if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
                (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            if (verbose != 0)
                log_info(LOG, "receive fails");
            return 1;
        }
        //parse response of ACT here
        int offset = 0;
        memset(header, 0, sizeof (struct pheader));
        memcpy(header, buf, sizeof (struct pheader));
        offset += sizeof (struct pheader);
        if(is_valid_act_response(header) == -1){
            if (verbose != 0)
                log_info(LOG, "not a valid ACT response");
            goto out;
        }

        if(header->msgType == ACTNACK){
            if (verbose != 0)
                log_info(LOG, "error code: <%u>", buf[offset]);
            res = 1;
        }else if (header->msgType == ACTACK){
            struct packet_ack ack_msg;
            memcpy(&ack_msg, buf + offset, sizeof (struct packet_ack));
            offset += sizeof (struct packet_ack);

            //get src addr
            char src_addr[INET6_ADDRSTRLEN];
    
            if (their_addr.ss_family == AF_INET){
                struct sockaddr_in *sock = (struct sockaddr_in *)&their_addr;
                inet_ntop(AF_INET, &(sock->sin_addr), src_addr, INET_ADDRSTRLEN);
            }
    
            if (their_addr.ss_family == AF_INET6){
                struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&their_addr;
                inet_ntop(AF_INET6, &(sock->sin6_addr), src_addr, INET6_ADDRSTRLEN);
            }

            char ccnd_addr[INET6_ADDRSTRLEN];
            struct sockaddr sa;
            if((header->ver & 32) == 0){
                sa.sa_family = AF_INET;
                struct sockaddr_in *gw_addr = (struct sockaddr_in *)&sa;
            
                memcpy(&(gw_addr->sin_addr), &(ack_msg.addr), sizeof(struct in_addr));
                get_ip_str(&sa, ccnd_addr, INET_ADDRSTRLEN);
            }
            else{
                if (verbose != 0)
                    log_info(LOG, "Sorry, this join_ndn cannot support V6");
                res = 2;
                goto out;
//
//                sa.sa_family = AF_INET6;
//                struct sockaddr_in6 *gw_addr = (struct sockaddr_in6 *)&sa;
//            
//                memcpy(&(gw_addr->sin6_addr), &(ack_msg.addr), sizeof(struct in6_addr));
//                get_ip_str(&sa, ccnd_addr, INET6_ADDRSTRLEN);
//
            }
            if (verbose != 0)
                log_info(LOG, "Recv ACK from %s, dst ccnd is running on %s:%d", src_addr, ccnd_addr, ntohs(ack_msg.port));
            construct_fib2gw(src_addr, ccnd_addr, ntohs(ack_msg.port), md);
            res = 0;
        }
    }

    freeaddrinfo(servinfo);

out:
    free(buf);
    free(header);
    free(act);

    close(sockfd);

    return res;

}
*/

int
connect_ndn2(struct mydata *md){
    struct ndngw *pos = md->gws;
    
    while(pos != NULL){
        struct prefixes *list = pos->nameprefix;
        while(list != NULL){
            char command[255];
            if (pos->protocol == 6)
                sprintf(command, "ccndc add %s tcp %s %s", list->content, pos->addr, pos->port);
            else
                sprintf(command, "ccndc add %s udp %s %s", list->content, pos->addr, pos->port);
            if (verbose != 0)
                log_info(LOG, "Execute command [%s]", command);
            int res = system(command);
            list = list->next;
        }
        pos = pos->next;
    }


    char namespace_config[255];
    char *home;
    FILE *fp = NULL;
    home = getenv("HOME");
    if (home != NULL){
        sprintf(namespace_config, "%s/.ccnx/ndn_namespace.conf", home);
        fp = fopen(namespace_config, "w");

    }else{
        fp = fopen(NDN_NAMESPACE_FILE, "w");
    }

    if(fp == NULL){
        if (verbose != 0)
            log_info(LOG, "cannot open file %s", namespace_config);
        exit(1);
    }

    fprintf(fp, "NDN_NAMESPACE %s/%s\n", md->namelist->content, md->id);
    fclose(fp);   
    return 0;
}
int send_to_agent(char *srvIP, char *srvPort, struct mydata *md){
    if(md == NULL || md->myip == NULL)
        return 1;
    int res = 1;
    md->state = NDNPARA;//unicast
    md->dst = srvIP;
    md->dst_port = srvPort;
    md->timeout = 9;
    md->retry_times = 2;
    
    if(verbose != 0)
        log_info(LOG, "Try to get NDN paras from NAC server %s", md->dst);

    res = send_inform(md);
    return res;
}
/*
int send_to_agent(char *srvIP, char *srvPort, struct mydata *md){
    int sockfd, numbytes;  
    char buf[MAXBUFLEN];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];
    char *myip = md->myip;

    if (srvIP == NULL || srvPort == NULL)
        return 1;    

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(srvIP, srvPort, &hints, &servinfo)) != 0) {
            if (verbose != 0)
            log_info(LOG, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
                        if (verbose != 0)
                perror("send to agent: socket");
            continue;
        }
        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
                        if (verbose != 0)
                perror("send to agent: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
            if (verbose != 0)
            log_info(LOG, "send to agent: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s);
    //printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo); // all done with this structure

    //send the MAC to the agent, hear contains the version and length
    //both of them are 2-byte long
    //struct agent_msg_header agent_header;
    //agent_header.version = htons(1);
    //agent_header.length = htons(4 + sizeof(struct hardware));

    //get the hardware address by ip
    //char face_name[INTERFACENAMELEN];
    //get_face_name(face_name, myip);
    //struct hardware myhw;
    //get_hw_addr(face_name, &myhw);
        
    //create the dhcp packet structure
    struct dhcp_packet out_packet;
    init_packet(&out_packet);

    //make the common parts, such as xid, ciadr et al.
    make_common_parts(&out_packet, myip);
    
    //make the options part, but right now only support domain name and private 246
    int offset;
    offset = make_options(&out_packet, NDNPARA);

    //DHCP_FIXED_NON_UDP includes all the field from op to the end of file, offset is the length of options
    int msg_len = DHCP_FIXED_NON_UDP + offset;

    memset(buf, 0, MAXBUFLEN);
    memcpy(buf, &out_packet, msg_len);
    //printf("HW len: %d\n", sizeof (struct hardware));
    //memcpy(buf, &agent_header, sizeof(struct agent_msg_header));
    //memcpy(buf + sizeof(struct agent_msg_header), &myhw, sizeof(struct hardware));

    //send(sockfd, buf, ntohs(agent_header.length), 0);
    send(sockfd, buf, msg_len, 0);

    memset(buf, 0, MAXBUFLEN);
    if ((numbytes = recv(sockfd, buf, MAXBUFLEN-1, 0)) == -1) {
            if (verbose != 0)
            perror("recv");
        return 1;
    }
    
    if (verbose != 0)
        log_info(LOG, "Recv response from NAC agent %s", s);

    struct dhcp_packet in_packet;
    init_packet(&in_packet);
    memcpy(&in_packet, buf, numbytes);

    //struct mydata *md = (struct mydata *)malloc(sizeof (struct mydata));
    //md->myip = NULL;
    //md->myport = "9695";
    //
    //md->naclist = NULL;
    //md->gws = NULL;
    //md->namelist = NULL;
    md->state = NDNPARA;

    int res = parse_options(&in_packet, md);
    //int res = parse_options(&in_packet, md);
    //if (res == 0){
    //    print_gws(md);
    //    print_namespace(md);
    //}

    //free(md);
    close(sockfd);
    if (res == -1)
        if (verbose != 0)
            log_info(LOG, "NAC agent cannot find NAC service in the fallback domain");
        
    return res;
}
*/

/*---------------------connection end-----------------------*/
/*
int
get_domain_level(char *domain){
    int i, level = 1;
    for (i = 0; i < strlen(domain); i ++){
        if(domain[i] == '.')
            level ++;
    }

    return level;
}
*/

int
get_dns_agents(struct mydata *md){
    char *domain = md->fallback_domain;
    char service[255];
    int status;
    ares_channel channel;
    struct ares_options options;
    int optmask = 0;
    struct ares_srv_reply *rr = NULL;

    status = ares_library_init(ARES_LIB_INIT_ALL);
    if(status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "DNS - ares_library_init: %s", ares_strerror(status));
        return 1;
    }

    options.sock_state_cb = state_cb;
    optmask |= ARES_OPT_SOCK_STATE_CB;

    status = ares_init_options(&channel, &options, optmask);
    if(status != ARES_SUCCESS){
        if (verbose != 0)
            log_info(LOG, "DNS - ares_init_options: %s", ares_strerror(status));
        return 1;
    }

    status = sprintf(service, "%s.%s", NAC_AGENT, domain);
    if(status < 0){
        if (verbose != 0)
            log_info(LOG, "DNS - cannot construct SRV_Name");
        return 1;
    }

    if (verbose != 0)
        log_info(LOG, "Querying service: %s", service);
    /*get the nac server list*/
    ares_query(channel, service, ns_c_in, ns_t_srv, srv_callback, &rr);
    wait_ares(channel);

    if(rr != NULL){//reorder the nacs according to the priority
        //choose one server: now we have the server list, choose the lower priority value
        struct ares_srv_reply *head;
        head = rr;
    
        struct agent *p = md->agents;
        while(head != NULL){
            //printf("host: %s, weight: %d, priority: %d, port: %d\n", head->host, head->weight, head->priority, head->port);            
            struct agent *new = (struct agent *)malloc(sizeof(struct agent));
            if (strlen(head->host) > 255){
                if (verbose != 0)
                    log_info(LOG, "Host name is too long\n");
                ares_free_data(rr);
                return 1;
            }
            strcpy(new->host, head->host);
            new->weight = head->weight;
            new->priority = head->priority;
            new->port = head->port;
            new->next = NULL;

            if (md->agents == NULL){
                md->agents = new;
                p = md->agents;
            }else{
                //move to the last one
                while (p->next != NULL)
                    p = p->next;

                p->next = new;
                p = p->next;
            }

            head = head -> next;
        }

        ares_free_data(rr);
        ares_destroy(channel);
        ares_library_cleanup();

        return 0;
    }
    else{
        ares_destroy(channel);
        ares_library_cleanup();
    
        return 1;
    }
}

int strip_leading_spaces(char *dst, char *src){
    if(dst == NULL || src == NULL)
        return -1;
    /* First remove leading spaces */
    const char* firstNonSpace = src;
    
    while(*firstNonSpace != '\0' && isspace(*firstNonSpace))
    {
        ++ firstNonSpace;
    }
    
    memcpy(dst, firstNonSpace, strlen(firstNonSpace)+1);
    return 0;
}


int read_config_file(char *filename, struct mydata *md){
    FILE *cfg;
    char buf[1024];
    char *cp;
    int count = 0, len;
    cfg = fopen(filename, "r");
    if(cfg == NULL){
        if (verbose != 0)
            log_info(stderr, "opening file %s failed", filename);
        return -1;
    }

    while (fgets((char*)buf, sizeof (buf), cfg)){
        len = strlen(buf);
        if(buf[0] == '#' || len == 0)//skip all lines starts with a #
            continue;
        
        if(buf[len -1] == '\n')
            buf[len - 1] = '\0';
        
        cp = index(buf, '#');//skip comments
        if(cp != NULL)
            *cp = '\0';
    
        if(strlen(buf) > 0){
            //save them into mydata
            char *res = (char *)malloc(len + 1);
            //strcpy(res, buf);
            int result = strip_leading_spaces(res, buf);
            if (result == 0)
                md->fallback_domain = res;
            else{
                free(res);
                return -1;
            }
            count++;
            break;
        }
    }
    fclose(cfg);
    if (count == 0)
        if (verbose != 0)
            log_info(LOG, "No domain is in the config file");

    return count;
}

int get_ip(char *addressBuffer){
    struct ifaddrs * ifAddrStruct = NULL;
    struct ifaddrs * ifa = NULL;
    void * tmpAddrPtr = NULL;
    int count = 0;

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa ->ifa_addr->sa_family == AF_INET) { // check it is IP4
            memset(addressBuffer, 0, INET6_ADDRSTRLEN);
            // is a valid IP4 Address
            tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            //char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
            if (strcmp(addressBuffer, "127.0.0.1") != 0) {
                //printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
                count = 1;
                break;
            }
        }/* else if (ifa->ifa_addr->sa_family==AF_INET6) { // check it is IP6
            // is a valid IP6 Address
            tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addressBuffer[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } */
    }
    if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
    return count;
}

void
ccndc_warn(int lineno, const char *format, ...)
{
    struct timeval t;
    va_list ap;
    va_start(ap, format);
    gettimeofday(&t, NULL);
    fprintf(stderr, "%d.%06d ccndc[%d]:%d: ", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), lineno);
    vfprintf(stderr, format, ap);
    va_end(ap);
}

void
ccndc_fatal(int line, const char *format, ...)
{
    struct timeval t;
    va_list ap;
    va_start(ap, format);
    gettimeofday(&t, NULL);
    fprintf(stderr, "%d.%06d ccndc[%d]:%d: ", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), line);
    vfprintf(stderr, format, ap);
    va_end(ap);
    exit(1);
}

#define ON_ERROR_EXIT(resval, msg) {                                    \
int _resval = (resval);                                             \
if (_resval < 0)                                                     \
ccndc_fatal(__LINE__, "fatal error, res = %d, %s\n", _resval, msg);  \
}

int
ccndc_get_ccnd_id(struct ccndc_data *self)
{
    struct ccn_charbuf *name = NULL;
    struct ccn_charbuf *resultbuf = NULL;
    struct ccn_parsed_ContentObject pcobuf = {0};
    char ccndid_uri[] = "ccnx:/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";
    const unsigned char *ccndid_result;
    int res = 0;
    
    name = ccn_charbuf_create();
    if (name == NULL) {
        ccndc_warn(__LINE__, "Unable to allocate storage for service locator name charbuf\n");
        return -1;
    }
    
    resultbuf = ccn_charbuf_create();
    if (resultbuf == NULL) {
        ccndc_warn(__LINE__, "Unable to allocate storage for result charbuf");
        res = -1;
        goto Cleanup;
    }
    
    res = ccn_name_from_uri(name, ccndid_uri);
    if (res < 0) {
        ccndc_warn(__LINE__, "Unable to parse service locator URI for ccnd key");
        goto Cleanup;
    }
    
    res = ccn_get(self->ccn_handle,
                  name,
                  self->local_scope_template,
                  4500, resultbuf, &pcobuf, NULL, 0);
    if (res < 0) {
        ccndc_warn(__LINE__, "Unable to get key from ccnd");
        goto Cleanup;
    }
    
    res = ccn_ref_tagged_BLOB (CCN_DTAG_PublisherPublicKeyDigest,
                               resultbuf->buf,
                               pcobuf.offset[CCN_PCO_B_PublisherPublicKeyDigest],
                               pcobuf.offset[CCN_PCO_E_PublisherPublicKeyDigest],
                               &ccndid_result, &self->ccnd_id_size);
    
    if (res < 0) {
        ccndc_warn(__LINE__, "Unable to parse ccnd response for ccnd id");
        goto Cleanup;
    }
    
    if (self->ccnd_id_size > sizeof (self->ccnd_id))
    {
        ccndc_warn(__LINE__, "Incorrect size for ccnd id in response");
        goto Cleanup;
    }
    
    memcpy(self->ccnd_id, ccndid_result, self->ccnd_id_size);
    
Cleanup:
    ccn_charbuf_destroy(&name);
    ccn_charbuf_destroy(&resultbuf);
    return (res);
}

struct ccndc_data *
ccndc_initialize_data(void) {
    struct ccndc_data *self;
    const char *msg = "Unable to initialize ccndc";
    int res;
    
    self = calloc(1, sizeof(*self));
    if (self == NULL) {
        ON_ERROR_EXIT (-1, msg);
    }
    
    self->ccn_handle = ccn_create();
    ON_ERROR_EXIT(ccn_connect(self->ccn_handle, NULL), "Unable to connect to local ccnd");
    ON_ERROR_EXIT(ccndc_get_ccnd_id(self), "Unable to obtain ID of local ccnd");
    
    /* Set up an Interest template to indicate scope 1 (Local) */
    self->local_scope_template = ccn_charbuf_create();
    res = ccnb_element_begin(self->local_scope_template, CCN_DTAG_Interest);
    res |= ccnb_element_begin(self->local_scope_template, CCN_DTAG_Name);
    res |= ccnb_element_end(self->local_scope_template);    /* </Name> */
    res |= ccnb_tagged_putf(self->local_scope_template, CCN_DTAG_Scope, "1");
    res |= ccnb_element_end(self->local_scope_template);    /* </Interest> */
    ON_ERROR_EXIT(res, msg);
    
    /* Create a null name */
    self->no_name = ccn_charbuf_create();
    ON_ERROR_EXIT(ccn_name_init(self->no_name), msg);
    
    self->lifetime = (~0U) >> 1;
    
    return self;
}

void
ccndc_destroy_data(struct ccndc_data **data) {
    struct ccndc_data *self = *data;
    
    if (self != NULL) {
        ccn_charbuf_destroy(&self->no_name);
        ccn_charbuf_destroy(&self->local_scope_template);
        ccn_disconnect(self->ccn_handle);
        ccn_destroy(&self->ccn_handle);
        free(self);
        *data = NULL;
    }
}

int
get_id(struct mydata *md){
    struct ccndc_data *ccndc;
    int i;

    ccndc = ccndc_initialize_data();

    struct ccn_charbuf *id = ccn_charbuf_create();

    for (i = 0; i < ccndc->ccnd_id_size; i ++)
        ccn_charbuf_putf(id, "%02X", ccndc->ccnd_id[i] & 0xff);

    md->id = (char*)malloc(id->length + 1);
    memset(md->id, 0, id->length + 1);
    memcpy(md->id, id->buf, id->length);
    char *res = md->id;
    res[id->length] = '\0';

Cleanup:
    ccn_charbuf_destroy(&id);
    ccndc_destroy_data(&ccndc);
    return 0;
}

void run_ccnd(){
    FILE *fpipe;
    char *command="ps ax | grep ccnd | grep -v grep";
    char line[256];
    //If fpipe is NULL
    if (!(fpipe = (FILE*) popen(command, "r"))){
        perror("Problems with pipe");
        exit(1);
    }
                         
    if (fgets( line, sizeof line, fpipe) == NULL){
        int res = system("ccndstart");
        if (verbose != 0)
            log_info(LOG, "[join_ndn]: Try to run ccndstart (res = %d)", res);
        if (WEXITSTATUS(res) == 127 || WEXITSTATUS(res) == -1)
            exit(1);
    }
    pclose(fpipe);
}

int read_resolv_file(char *filename, char *domain){
    FILE *cfg;
    char buf[1024];
    char tmp[255];
    int flag = 0;
    cfg = fopen(filename, "r");
    if(cfg == NULL){
        return -1;
    }

    while (fgets((char*)buf, sizeof (buf), cfg)){
        char *token = NULL;
        char *running = buf;
        const char delimiters[] = " \n";
        token = strsep(&running, delimiters);
        if ( strcmp(token, "search") == 0){
            flag = 1;
            strcpy(domain, running);
            break;
        }

        if ( strcmp(token, "domain") == 0){
            flag = 2;
            strcpy(tmp, running);
        }
    }

    if (flag == 2)
        strcpy(domain, tmp);
    fclose(cfg);

    return flag;
}

int main(int argc, char **argv)
{
    int res = 1, len, ch;
    unsigned char *nacs, *gws, *namespace;
    char *log_dst = NULL;
    //struct nac *tmp = NULL;
    char *fallback_config = NULL;
    char address[INET6_ADDRSTRLEN];
    char domain_str[255];
    struct mydata *md = (struct mydata *)malloc(sizeof (struct mydata));
    memset(md, 0, sizeof (struct mydata));
    md->myip = NULL;
    md->id = NULL;
    md->myport = "9695";
    md->domain = NULL;
    md->dst = NULL;
    md->dst_port = NULL;

    md->naclist = NULL;
    md->gws = NULL;
    md->namelist = NULL;
    md->agents = NULL;
    md->fallback_domain = NULL;
    md->remote = 0;

    LOG = stdout;

    while ((ch = getopt(argc, argv, "a:p:s:g:n:d:l:f:hv")) != -1) {
       switch (ch){
           case 'a':
               md->myip = optarg;
                  break;
           case 's'://nacs string
               nacs = str2hex(optarg, &len);
               if(nacs != NULL){
                   parse_nacs(md, nacs, 0, len);
                   free(nacs);
                   print_nacs(md);
               }
                  break;
           case 'g'://gws string
               gws = str2hex(optarg, &len);
               if(gws != NULL){
                   parse_gws(md, gws, 0, len);
                   free(gws);
                   print_gws(md);
               }
                  break;
           case 'n'://namespace
               //namespace = str2hex(optarg, &len);
               len = strlen(optarg);
               if (len > 0){
                   namespace = (unsigned char *)malloc(len + 1);
                   memcpy(namespace, optarg, len);
                   parse_namespaces(md, namespace, 0, len);
                   free(namespace);
                   print_namespace(md);
               }
                  break;
           case 'p':
               md->myport = optarg;
                  break;
           case 'd':
               if (strlen(optarg) > 0)
                   md->domain = optarg;
                  break;
           case 'l':
               log_dst = optarg;
                  break;
           case 'f':
               fallback_config = optarg;
                  break;
           case 'v':
               verbose = 1;
                  break;
           case 'h':
           default:
               usage(argv[0]);
       }
    }

    run_ccnd();
    if (md->domain == NULL){
        md->domain = domain_str;
        res = read_resolv_file("/etc/resolv.conf", md->domain);
        if (res == 0)
            md->domain = NULL;
    }

    if (md->myip == NULL) {
        //log_info(LOG, "User MUST specify the IP address");
        if (verbose != 0)
            log_info(LOG, "[join_ndn]: User does not specify the IP address!");
        //usage(argv[0]);
        md->myip = address;
        if(get_ip(md->myip) == 0){
           res = system("ccndstop");
           exit(0);
        }        
    }

    if ((md->namelist == NULL && md->gws != NULL) || 
            (md->namelist != NULL && md->gws == NULL)){
        free_gws(md);
        free_namespaces(md);
    }

    if (log_dst != NULL){
        LOG = fopen(log_dst, "w");
        if (LOG == NULL){
            log_info(stderr, "cannot open log file");
            exit(1);
        }
        //LOG = mylog;
        log_info(LOG, "starting join_ndn");
    }

    //get the fallback agent domain if no fallback_domain is specified
    if (md->fallback_domain == NULL){
        char config_file[255];
        if (fallback_config == NULL){
            char *home;
            home = getenv("HOME");
            if (home != NULL){
                sprintf(config_file, "%s/.ccnx/join_ndn.conf", home);
                fallback_config = config_file;
            }
        }

        if (fallback_config != NULL){
            res = read_config_file(fallback_config, md);
            if (res == -1){
                if (verbose != 0)
                    log_info(LOG, "No join_ndn.conf found");
                md->fallback_domain = NULL;
            }
        }
    }

    res = 1;
    if (md->fallback_domain != NULL){
        if (verbose != 0)
            log_info(LOG, "Your fallback domain: %s", md->fallback_domain);
        res = 1;
    }

    my_xid = get_random();

    if (md->naclist != NULL) {
        if(md->gws == NULL && md->namelist == NULL){
            if (verbose != 0)
                log_info(LOG, "Learnt NAC servers from dhclient");
            res = try_nacs(md);
        } else if (md->gws != NULL && md->namelist != NULL){
            if (verbose != 0)
                log_info(LOG, "Learnt all NDN paras from dhclient");
            res = 0;
        }
    }

    if (res != 0){
        init_list(md);

        if (verbose == 0)
            printf("[join_ndn]: Searching NDN service ...\n");
        
        res = get_inform_nacs(md);
        if (res == 0){
            if (verbose != 0)
                print_nacs(md);
            res = try_nacs(md);
        }

        //if(res == 0){//print our all info
        //    print_gws(md);
        //    print_namespace(md);
        //}else if(md->domain != NULL){
        if(res != 0 && md->domain != NULL){
            init_list(md);
            
            //char domain[255], *next;
            char domain[255];
            strcpy(domain, md->domain);

            char *token = NULL;
            const char delimiters[] = " \n";
            token = strtok(domain, delimiters);
            while (token != NULL){
                if (verbose != 0)
                    log_info(LOG, "Search domain %s ...", token);
                res = get_dns_nacs(token, md);
                if (res == 0)
                    break;

                token = strtok(NULL, delimiters);
            }
            if (res == 0)
                res = try_nacs(md);

            if (res != 0)
                free_nacs(md);
/*
            int domain_lvl = get_domain_level(domain);
            int cur_lvl = domain_lvl, end_lvl = 2;
            
            res = get_dns_nacs(domain, md);
            while( res != 0 && cur_lvl > end_lvl ){
                if(domain_lvl == cur_lvl){
                    strtok_r(domain, ".", &next);
                }else{
                    strtok_r(NULL, ".", &next);
                }

                res = get_dns_nacs(next, md);
                cur_lvl --;
            }
*/            
       }
    }

    if (res != 0 && md->fallback_domain != NULL){
        if (verbose != 0)
            log_info(LOG, "No NDN service found in the local subnet, try to find fallback NAC agent");
        //find the fallback agent
        //md->fallback_domain = "netsec.colostate.edu";

        //need to locate my NAC agent first
        res = get_dns_agents(md);
        //get the remote NAC server 
        //res = get_dns_nacs(md->fallback_domain, md);

        struct agent *tmp = md->agents;
        //struct nac *tmp = md->naclist;
        while(tmp != NULL && res == 0){
            char agent_port[10];
            sprintf(agent_port, "%d", tmp->port);
            res = send_to_agent(tmp->host, agent_port, md);
            //res = send_to_agent("192.168.1.100", "39695", md);
            //res = try_nacs(md);
            md->remote = 1;
            if (res == 0)
                break;
            tmp = tmp->next;
        }

        if (res !=0)
            free_agents(md);
    }

    if (res == 0){//connect to NDN network
        printf("[join_ndn]: Connecting to the NDN network ...\n");
        
        //res = try_nacs(md);
/*
        struct ndngw *cur = md->gws;
        while(cur != NULL){
            connect_ndn(md, cur->addr, cur->port);
            cur = cur->next;
        }
*/
        get_id(md);
        connect_ndn2(md);
        if (verbose != 0)
            print_gws(md); 

        print_namespace(md); 
    }else{
        if (verbose != 0)
            log_info(LOG, "No NDN service found in this try");
        else
            printf("[join_ndn]: No NDN service found.\n");
    }

    if (md->fallback_domain != NULL)
        free(md->fallback_domain);
    //also need to free the space for gw and namespace
    init_list(md);
    free(md);

    if (LOG != stdout)
        fclose(LOG);

    return 0;
}
